#include <zenox/home.hpp>
#include <zenox/minilog.hpp>
#include <algorithm>
#include <concepts>
#include <cstring>
#include <string>
#if defined(_WIN32)
#include <windows.h>
#elif defined(__linux__)
#include <unistd.h>
#endif

namespace zenox {

static std::string calculate_executable_dir() {
#if defined(_WIN32)
    WCHAR buffer[MAX_PATH + 1];
    DWORD len = GetModuleFileNameW(NULL, buffer, MAX_PATH);
    if (len != 0) [[likely]] {
        buffer[len] = '\0';
        std::wstring executableDir = buffer;
        size_t lastSlashIndex = executableDir.find_last_of(L"\\/");
        executableDir = executableDir.substr(0, lastSlashIndex);
        return pathu8(std::filesystem::path(executableDir));
    }
#elif defined(__linux__)
    char buffer[PATH_MAX + 1];
    ssize_t len = readlink("/proc/self/exe", buffer, PATH_MAX);
    if (len != -1) [[likely]] {
        buffer[len] = '\0';
        std::string executableDir = buffer;
        size_t lastSlashIndex = executableDir.find_last_of("/");
        return executableDir.substr(0, lastSlashIndex);
    }
#endif
    return ".";
}

std::string const &zeno_executable_dir() {
    static const auto execdir = calculate_executable_dir();
    return execdir;
}

static std::string calculate_zeno_home() {
    auto executable_dir = u8path(zeno_executable_dir());
    if (!std::filesystem::is_directory(executable_dir)) [[unlikely]] {
        executable_dir = std::filesystem::current_path();
    }
    executable_dir = std::filesystem::canonical(executable_dir);
#ifdef ZENO_SOURCE_DIR
    if (auto source_dir = u8path(ZENO_SOURCE_DIR); std::filesystem::is_directory(source_dir)) {
        source_dir = std::filesystem::canonical(source_dir);
        if (auto dir = pathu8(source_dir); pathu8(executable_dir).starts_with(dir)) [[likely]] {
            return dir;
        }
    }
#endif
    // executable_dir is /usr/local/bin, while home need to be /usr/local
    return pathu8(std::filesystem::canonical(executable_dir.parent_path()));
}

std::string const &zeno_home() {
    static const auto home = calculate_zeno_home();
    return home;
}

std::string_view zeno_source_dir_prefix_remove(std::string_view filename) {
#ifdef ZENO_SOURCE_DIR // remove possible zeno repo path prefix from source file path
#ifndef _WIN32
    static
#endif
    auto zeno_source_dir = pathu8(u8path(ZENO_SOURCE_DIR));
#ifdef _WIN32
    std::replace(zeno_source_dir.begin(), zeno_source_dir.end(), '/', '\\');
#endif
    if (filename.starts_with(zeno_source_dir)) {
        filename.remove_prefix(zeno_source_dir.size());
    }
#endif
    return filename;
}

static std::string calculate_zeno_build_date() {
    const char date[] = __DATE__;
    static const char *table[] = {
        "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
    };
    if (sizeof(date) != 12) {
        return date;
    }
    int month = std::find(table, table + 12, std::string(date, 3)) - table + 1;
    int day = std::stoi(std::string(date + 4, 2));
    int year = std::stoi(std::string(date + 7, 4));
    return zenox::format("{:04d}.{:02d}.{:02d}", year, month, day);
}

std::string const &zeno_build_date() {
    static const auto date = calculate_zeno_build_date();
    return date;
}

std::string touch_directory_if_not_exist(std::string_view path) {
    auto p = u8path(path);
    std::filesystem::create_directories(p.parent_path());
    return pathu8(p);
}

std::string zeno_home_relative(std::string_view relative) {
    return pathu8(u8path(zeno_home()) / u8path(relative));
}

std::string zeno_executable_dir_relative(std::string_view relative) {
    return pathu8(u8path(zeno_executable_dir()) / u8path(relative));
}

}
